// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package qunar.tc.decompiler.modules.decompiler;

import qunar.tc.decompiler.struct.gen.MethodDescriptor;
import qunar.tc.decompiler.struct.gen.VarType;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ClasspathHelper {

    private static final Map<String, Method> METHOD_CACHE = Collections.synchronizedMap(new HashMap<String, Method>());

    public static Method findMethod(String classname, String methodName, MethodDescriptor descriptor) {
        String targetClass = classname.replace('/', '.');
        String methodSignature = buildMethodSignature(targetClass + '.' + methodName, descriptor);

        Method method;
        if (METHOD_CACHE.containsKey(methodSignature)) {
            method = METHOD_CACHE.get(methodSignature);
        } else {
            method = findMethodOnClasspath(targetClass, methodSignature);
            METHOD_CACHE.put(methodSignature, method);
        }

        return method;
    }

    private static Method findMethodOnClasspath(String targetClass, String methodSignature) {
        try {
            // use bootstrap classloader to only provide access to JRE classes
            Class cls = new ClassLoader(null) {
            }.loadClass(targetClass);
            for (Method mtd : cls.getMethods()) {
                // use contains() to ignore access modifiers and thrown exceptions
                if (mtd.toString().contains(methodSignature)) {
                    return mtd;
                }
            }
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

    private static String buildMethodSignature(String name, MethodDescriptor md) {
        StringBuilder sb = new StringBuilder();

        appendType(sb, md.ret);
        sb.append(' ').append(name).append('(');
        for (VarType param : md.params) {
            appendType(sb, param);
            sb.append(',');
        }
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.setLength(sb.length() - 1);
        }
        sb.append(')');

        return sb.toString();
    }

    private static void appendType(StringBuilder sb, VarType type) {
        sb.append(type.value.replace('/', '.'));
        for (int i = 0; i < type.arrayDim; i++) {
            sb.append("[]");
        }
    }
}
